package com.hmdp.utils;


import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.Shop;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Component
public class RedisClient {


    private final StringRedisTemplate stringRedisTemplate;

    public RedisClient(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    //逻辑过期存储
    public void setWithLogic(String key, Object value, Long time, TimeUnit unit) {
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        redisData.setData(value);

        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
    }

    //缓存穿透
    public <R, K> R redisWithPassThrough(String prefix, K id, Class<R> type, Function<K,R> queryDB, Long time, TimeUnit unit){

        String redisKey = prefix + id;
        String json = stringRedisTemplate.opsForValue().get(redisKey);


        //查询命中且不为""
        if (json != null && !json.isEmpty()) {
            //缓存中查询到了，直接返回
            return JSONUtil.toBean(json, type);
        }
        //缓存命中，判断是否为空值: ""
        if (json != null) {
            return null;
        }

        //查询数据库
        R data = queryDB.apply(id);
        //判断if是否存在
        if (data == null) {
            //查询结构为空，设置空对象
            stringRedisTemplate.opsForValue().set(redisKey, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }

        String dataJson= JSONUtil.toJsonStr(data);

        stringRedisTemplate.opsForValue().set(redisKey, dataJson,time, unit);

        return data;
    }


    //逻辑过期来解决缓存击穿
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    public  <R,T> R redisWithLogicExpire(String prefix, T id, Class<R> type, Function<T,R> queryDB, Long time, TimeUnit unit){
        //默认key-value被提前存储并且永远不会过期
        String key =prefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isBlank(json)){
            return null;
        }

        //判断当前信息是否过期
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);

        //转换数据类型
        JSONObject rJson = (JSONObject) redisData.getData();
        R r = JSONUtil.toBean(rJson, type);
        LocalDateTime expireTime = redisData.getExpireTime();
        if(LocalDateTime.now().isAfter(expireTime)){
            //当前时间大于过期时间，尝试获取锁
            String lockKey = RedisConstants.LOCK_SHOP_KEY+id;
            if(getKeyLock(lockKey)){
                //获取成功
                CACHE_REBUILD_EXECUTOR.submit(()->{
                    try {
                        //从数据库取出新数据
                        R curValue = queryDB.apply(id);
                        //存入缓存
                        this.setWithLogic(key, curValue, time,unit);
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        unLock(lockKey);
                    }
                });
            }
        }

        //返回当获取数据
        return r;
    }
    private boolean getKeyLock(String id) {
        //尝试上锁
        Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(RedisConstants.LOCK_SHOP_KEY + id, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(getLock);
    }

    private void unLock(String id) {
        //释放锁
        stringRedisTemplate.delete(RedisConstants.LOCK_SHOP_KEY + id);
    }

}
